/*
 *   Licensed to the Apache Software Foundation (ASF) under one
 *   or more contributor license agreements.  See the NOTICE file
 *   distributed with this work for additional information
 *   regarding copyright ownership.  The ASF licenses this file
 *   to you under the Apache License, Version 2.0 (the
 *   "License"); you may not use this file except in compliance
 *   with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing,
 *   software distributed under the License is distributed on an
 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *   KIND, either express or implied.  See the License for the
 *   specific language governing permissions and limitations
 *   under the License.
 *
 */

package org.apache.directory.http.client;


import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.NetworkSchemaLoader;
import org.apache.directory.server.codec.json.JsonBuilder;
import org.apache.directory.server.core.CoreSession;
import org.apache.directory.server.http.AppConstants;
import org.apache.directory.shared.asn1.primitives.OID;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.cursor.Cursor;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.exception.LdapException;
import org.apache.directory.shared.ldap.filter.SearchScope;
import org.apache.directory.shared.ldap.message.AbandonRequest;
import org.apache.directory.shared.ldap.message.AbstractResultResponse;
import org.apache.directory.shared.ldap.message.AddRequest;
import org.apache.directory.shared.ldap.message.AddRequestImpl;
import org.apache.directory.shared.ldap.message.AddResponse;
import org.apache.directory.shared.ldap.message.AliasDerefMode;
import org.apache.directory.shared.ldap.message.BindRequest;
import org.apache.directory.shared.ldap.message.BindRequestImpl;
import org.apache.directory.shared.ldap.message.BindResponse;
import org.apache.directory.shared.ldap.message.BindResponseImpl;
import org.apache.directory.shared.ldap.message.CompareRequest;
import org.apache.directory.shared.ldap.message.CompareRequestImpl;
import org.apache.directory.shared.ldap.message.CompareResponse;
import org.apache.directory.shared.ldap.message.DeleteRequest;
import org.apache.directory.shared.ldap.message.DeleteRequestImpl;
import org.apache.directory.shared.ldap.message.DeleteResponse;
import org.apache.directory.shared.ldap.message.ExtendedRequest;
import org.apache.directory.shared.ldap.message.ExtendedResponse;
import org.apache.directory.shared.ldap.message.Message;
import org.apache.directory.shared.ldap.message.ModifyDnRequest;
import org.apache.directory.shared.ldap.message.ModifyDnRequestImpl;
import org.apache.directory.shared.ldap.message.ModifyDnResponse;
import org.apache.directory.shared.ldap.message.ModifyRequest;
import org.apache.directory.shared.ldap.message.ModifyRequestImpl;
import org.apache.directory.shared.ldap.message.ModifyResponse;
import org.apache.directory.shared.ldap.message.Response;
import org.apache.directory.shared.ldap.message.ResultResponseRequest;
import org.apache.directory.shared.ldap.message.SearchRequest;
import org.apache.directory.shared.ldap.message.SearchRequestImpl;
import org.apache.directory.shared.ldap.message.SearchResultEntry;
import org.apache.directory.shared.ldap.message.control.Control;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.shared.ldap.name.RDN;
import org.apache.directory.shared.ldap.schema.SchemaManager;
import org.apache.directory.shared.ldap.schema.manager.impl.DefaultSchemaManager;
import org.apache.directory.shared.ldap.util.StringTools;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * TODO LdapHttpConnection.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class LdapHttpConnection implements LdapConnection
{

    /** the SchemaManager */
    private SchemaManager schemaManager;

    private AtomicInteger messageId = new AtomicInteger( 0 );

    private static final Logger LOG = LoggerFactory.getLogger( LdapHttpConnection.class );

    private URL serviceUrl;

    /** list of controls supported by the server */
    private List<String> supportedControls;

    /** The ROOT DSE entry */
    private Entry rootDSE;

    private HttpClient client;

    private String bindUri;

    private String searhUri;

    private String modifyUri;

    private String modifyDnUri;

    private String unBindUri;

    private String addUri;

    private String deleteUri;

    private String compareUri;
    
    private Header sessionHeader;


    public LdapHttpConnection( URL url ) throws Exception
    {
        serviceUrl = url;

        bindUri = url + AppConstants.BIND_URI;
        searhUri = url + AppConstants.SEARCH_URI;
        modifyUri = url + AppConstants.MODIFY_URI;
        modifyDnUri = url + AppConstants.MODIFY_DN_URI;
        unBindUri = url + AppConstants.UNBIND_URI;
        addUri = url + AppConstants.ADD_URI;
        deleteUri = url + AppConstants.DELETE_URI;
        compareUri = url + AppConstants.COMPARE_URI;
        
        client = new DefaultHttpClient();
    }


    /**
     * {@inheritDoc}
     */
    public boolean close() throws IOException
    {
        try
        {
            unBind();
        }
        catch ( Exception e )
        {
            throw new IOException( e.getMessage() );
        }

        return true;
    }


    /**
     * {@inheritDoc}
     */
    public boolean connect() throws LdapException, IOException
    {
        if ( client == null )
        {
            client = new DefaultHttpClient();
        }

        return true;
    }


    /**
     * {@inheritDoc}
     */
    public AddResponse add( AddRequest addRequest ) throws LdapException
    {
        try
        {
            int newId = messageId.incrementAndGet();
            addRequest.setMessageId( newId );

            HttpPost post = new HttpPost( addUri );
            post.setHeader( sessionHeader );
            post.setEntity( new StringEntity( JsonBuilder.toJson( addRequest ) ) );

            HttpResponse response = client.execute( post );

            String json = EntityUtils.toString( response.getEntity() );
            System.out.println( json );
            
            AddResponse resp = ( AddResponse ) JsonBuilder.fromJson( json, AbstractResultResponse.class );

            return resp;
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }
    }


    /**
     * {@inheritDoc}
     */
    public AddResponse add( Entry entry ) throws LdapException
    {
        AddRequest addRequest = new AddRequestImpl();
        addRequest.setEntry( entry );

        return add( addRequest );
    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( CompareRequest compareRequest ) throws LdapException
    {
        try
        {
            int newId = messageId.incrementAndGet();
            compareRequest.setMessageId( newId );
            
            HttpPost post = new HttpPost( compareUri );
            post.setHeader( sessionHeader );
            
            post.setEntity( new StringEntity( JsonBuilder.toJson( compareRequest ) ) );
            HttpResponse response = client.execute( post );
            String json = EntityUtils.toString( response.getEntity() );
            
            CompareResponse resp = ( CompareResponse ) JsonBuilder.fromJson( json, AbstractResultResponse.class );
            return resp;
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }

    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( DN dn, String attributeName, byte[] value ) throws LdapException
    {
        CompareRequest compareRequest = new CompareRequestImpl();
        compareRequest.setName( dn );
        compareRequest.setAttributeId( attributeName );
        compareRequest.setAssertionValue( value );

        return compare( compareRequest );
    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( DN dn, String attributeName, String value ) throws LdapException
    {
        CompareRequest compareRequest = new CompareRequestImpl();
        compareRequest.setName( dn );
        compareRequest.setAttributeId( attributeName );
        compareRequest.setAssertionValue( value );

        return compare( compareRequest );
    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( String dn, String attributeName, byte[] value ) throws LdapException
    {
        return compare( new DN( dn ), attributeName, value );
    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( String dn, String attributeName, String value ) throws LdapException
    {
        return compare( new DN( dn ), attributeName, value );
    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( DN dn, String attributeName, Value<?> value ) throws LdapException
    {
        CompareRequest compareRequest = new CompareRequestImpl();
        compareRequest.setName( dn );
        compareRequest.setAttributeId( attributeName );

        if ( value.isBinary() )
        {
            compareRequest.setAssertionValue( value.getBytes() );
        }
        else
        {
            compareRequest.setAssertionValue( value.getString() );
        }

        return compare( compareRequest );
    }


    /**
     * {@inheritDoc}
     */
    public CompareResponse compare( String dn, String attributeName, Value<?> value ) throws LdapException
    {
        return compare( new DN( dn ), attributeName, value );
    }


    /**
     * {@inheritDoc}
     */
    public DeleteResponse delete( DeleteRequest deleteRequest ) throws LdapException
    {
        try
        {
            int newId = messageId.incrementAndGet();
            deleteRequest.setMessageId( newId );

            HttpPost post = new HttpPost( deleteUri );
            post.setHeader( sessionHeader );

            post.setEntity( new StringEntity( JsonBuilder.toJson( deleteRequest ) ) );
            HttpResponse response = client.execute( post );
            String json = EntityUtils.toString( response.getEntity() );

            DeleteResponse resp = ( DeleteResponse ) JsonBuilder.fromJson( json, AbstractResultResponse.class );

            return resp;
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }
    }


    /**
     * {@inheritDoc}
     */
    public DeleteResponse delete( DN dn ) throws LdapException
    {
        DeleteRequest deleteRequest = new DeleteRequestImpl();
        deleteRequest.setName( dn );

        return delete( deleteRequest );
    }


    /**
     * {@inheritDoc}
     */
    public DeleteResponse delete( String dn ) throws LdapException
    {
        return delete( new DN( dn ) );
    }


    /**
     * {@inheritDoc}
     */
    public boolean doesFutureExistFor( Integer messageId )
    {
        return false;
    }


    /**
     * {@inheritDoc}
     */
    public SchemaManager getSchemaManager()
    {
        return schemaManager;
    }


    /**
     * {@inheritDoc}
     */
    public List<String> getSupportedControls() throws LdapException
    {
        if ( supportedControls != null )
        {
            return supportedControls;
        }

        if ( rootDSE == null )
        {
            fetchRootDSE();
        }

        supportedControls = new ArrayList<String>();

        EntryAttribute attr = rootDSE.get( SchemaConstants.SUPPORTED_CONTROL_AT );

        for ( Value<?> value : attr )
        {
            supportedControls.add( value.getString() );
        }

        return supportedControls;
    }


    /**
     * {@inheritDoc}
     */
    public boolean isAuthenticated()
    {
        return ( sessionHeader != null );
    }


    /**
     * {@inheritDoc}
     */
    public boolean isConnected()
    {
        return true;
    }


    /**
     * {@inheritDoc}
     */
    public boolean isControlSupported( String controlOID ) throws LdapException
    {
        return false;
    }


    /**
     * {@inheritDoc}
     */
    public void loadSchema() throws LdapException
    {
        try
        {
            NetworkSchemaLoader loader = new NetworkSchemaLoader( this );
            schemaManager = new DefaultSchemaManager( loader );

            boolean loaded = schemaManager.loadAllEnabled();
            if ( !loaded )
            {
                LOG.debug( "problems encountered while initializing the schema manager {}", schemaManager.getErrors() );
            }

            LOG.debug( "schema manager was initialized successfully" );
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }
    }


    /**
     * {@inheritDoc}
     */
    public Entry lookup( DN dn, String... attributes ) throws LdapException
    {
        Entry entry = null;

        try
        {
            Cursor<Response> cursor = search( dn, "(objectClass=*)", SearchScope.OBJECT, attributes );

            // Read the response
            if ( cursor.next() )
            {
                // cursor will always hold SearchResultEntry objects cause there is no ManageDsaITControl passed with search request
                entry = ( ( SearchResultEntry ) cursor.get() ).getEntry();
            }

            // Pass through the SaerchResultDone, or stop
            // if we have other responses
            cursor.next();

            // And close the cursor
            cursor.close();
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }

        return entry;
    }


    /**
     * {@inheritDoc}
     */
    public Entry lookup( String dn, String... attributes ) throws LdapException
    {
        return lookup( new DN( dn ), attributes );
    }


    /**
     * {@inheritDoc}
     */
    public Entry lookup( DN dn ) throws LdapException
    {
        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
    }


    /**
     * {@inheritDoc}
     */
    public Entry lookup( String dn ) throws LdapException
    {
        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyResponse modify( DN dn, Modification... modifications ) throws LdapException
    {
        if ( dn == null )
        {
            LOG.debug( "received a null dn for modification" );
            throw new IllegalArgumentException( "The DN to be modified cannot be null" );
        }

        ModifyRequest modReq = new ModifyRequestImpl();
        modReq.setName( dn );

        for ( Modification modification : modifications )
        {
            modReq.addModification( modification );
        }

        return modify( modReq );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyResponse modify( String dn, Modification... modifications ) throws LdapException
    {
        return modify( new DN( dn ), modifications );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyResponse modify( Entry entry, ModificationOperation modOp ) throws LdapException
    {
        if ( entry == null )
        {
            LOG.debug( "received a null entry for modification" );
            throw new IllegalArgumentException( "Entry to be modified cannot be null" );
        }

        ModifyRequest modReq = new ModifyRequestImpl();
        modReq.setName( entry.getDn() );

        Iterator<EntryAttribute> itr = entry.iterator();
        while ( itr.hasNext() )
        {
            modReq.addModification( itr.next(), modOp );
        }

        return modify( modReq );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyResponse modify( ModifyRequest modRequest ) throws LdapException
    {
        try
        {
            int newId = messageId.incrementAndGet();
            modRequest.setMessageId( newId );

            HttpPost post = new HttpPost( modifyUri );
            post.setHeader( sessionHeader );

            post.setEntity( new StringEntity( JsonBuilder.toJson( modRequest ) ) );

            HttpResponse response = client.execute( post );

            String json = EntityUtils.toString( response.getEntity() );

            ModifyResponse resp = ( ModifyResponse ) JsonBuilder.fromJson( json, AbstractResultResponse.class );

            return resp;
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }
    }


    /**
     * {@inheritDoc}
     * WARNING: this method is not in compatible with CoreSession API in some cases
     *          cause this we call {@link CoreSession#move(ModifyDnRequest)} always from this method.
     *          Instead use other specific modifyDn operations like {@link #move(DN, DN)}, {@link #rename(DN, RDN)} etc..
     */
    public ModifyDnResponse modifyDn( ModifyDnRequest modDnRequest ) throws LdapException
    {

        try
        {
            int newId = messageId.incrementAndGet();
            modDnRequest.setMessageId( newId );

            HttpPost post = new HttpPost( modifyDnUri );
            post.setHeader( sessionHeader );
            post.setEntity( new StringEntity( JsonBuilder.toJson( modDnRequest ) ) );

            HttpResponse response = client.execute( post );
            String json = EntityUtils.toString( response.getEntity() );

            ModifyDnResponse resp = ( ModifyDnResponse ) JsonBuilder.fromJson( json, AbstractResultResponse.class );
            return resp;
        }
        catch ( Exception e )
        {
            throw new LdapException( e );
        }
    }


    /**
     * {@inheritDoc}
     */
    public ModifyDnResponse move( DN entryDn, DN newSuperiorDn ) throws LdapException
    {
        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
        modDnRequest.setName( entryDn );
        modDnRequest.setNewSuperior( newSuperiorDn );

        //TODO not setting the below value is resulting in error
        //modDnRequest.setNewRdn( entryDn.getRdn() );

        return modifyDn( modDnRequest );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyDnResponse move( String entryDn, String newSuperiorDn ) throws LdapException
    {
        return move( new DN( entryDn ), new DN( newSuperiorDn ) );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyDnResponse rename( DN entryDn, RDN newRdn, boolean deleteOldRdn ) throws LdapException
    {
        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
        modDnRequest.setName( entryDn );
        modDnRequest.setNewRdn( newRdn );
        modDnRequest.setDeleteOldRdn( deleteOldRdn );

        return modifyDn( modDnRequest );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyDnResponse rename( DN entryDn, RDN newRdn ) throws LdapException
    {
        return rename( entryDn, newRdn, false );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyDnResponse rename( String entryDn, String newRdn, boolean deleteOldRdn ) throws LdapException
    {
        return rename( new DN( entryDn ), new RDN( newRdn ), deleteOldRdn );
    }


    /**
     * {@inheritDoc}
     */
    public ModifyDnResponse rename( String entryDn, String newRdn ) throws LdapException
    {
        return rename( new DN( entryDn ), new RDN( newRdn ) );
    }


    /**
     * Moves and renames the given entryDn.The old RDN will be deleted
     *
     * @see #moveAndRename(DN, DN, boolean)
     */
    public ModifyDnResponse moveAndRename( DN entryDn, DN newDn ) throws LdapException
    {
        return moveAndRename( entryDn, newDn, true );
    }


    /**
     * Moves and renames the given entryDn.The old RDN will be deleted
     *
     * @see #moveAndRename(DN, DN, boolean)
     */
    public ModifyDnResponse moveAndRename( String entryDn, String newDn ) throws LdapException
    {
        return moveAndRename( new DN( entryDn ), new DN( newDn ), true );
    }


    /**
     * Moves and renames the given entryDn.The old RDN will be deleted if requested
     *
     * @param entryDn The original entry DN
     * @param newDn The new Entry DN
     * @param deleteOldRdn Tells if the old RDN must be removed
     */
    public ModifyDnResponse moveAndRename( DN entryDn, DN newDn, boolean deleteOldRdn ) throws LdapException
    {
        // Check the parameters first
        if ( entryDn == null )
        {
            throw new IllegalArgumentException( "The entry DN must not be null" );
        }

        if ( entryDn.isRootDSE() )
        {
            throw new IllegalArgumentException( "The RootDSE cannot be moved" );
        }

        if ( newDn == null )
        {
            throw new IllegalArgumentException( "The new DN must not be null" );
        }

        if ( newDn.isRootDSE() )
        {
            throw new IllegalArgumentException( "The RootDSE cannot be the target" );
        }

        // Create the request
        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
        modDnRequest.setName( entryDn );
        modDnRequest.setNewRdn( newDn.getRdn() );
        modDnRequest.setNewSuperior( newDn.getParent() );
        modDnRequest.setDeleteOldRdn( deleteOldRdn );

        return modifyDn( modDnRequest );
    }


    /**
     * Moves and renames the given entryDn.The old RDN will be deleted if requested
     *
     * @param entryDn The original entry DN
     * @param newDn The new Entry DN
     * @param deleteOldRdn Tells if the old RDN must be removed
     */
    public ModifyDnResponse moveAndRename( String entryDn, String newDn, boolean deleteOldRdn ) throws LdapException
    {
        return moveAndRename( new DN( entryDn ), new DN( newDn ), deleteOldRdn );
    }


    /**
     * {@inheritDoc}
     */
    public Cursor<Response> search( SearchRequest searchRequest ) throws LdapException
    {
        try
        {
            int newId = messageId.incrementAndGet();

            searchRequest.setMessageId( newId );

            HttpPost post = new HttpPost( searhUri );
            post.addHeader( sessionHeader );
            post.setEntity( new StringEntity( JsonBuilder.get().toJson( searchRequest ) ) );

            HttpResponse resp = client.execute( post );
            String json = EntityUtils.toString( resp.getEntity() );
            //            System.out.println( json );

            return new JsonEntryToResponseCursor<Response>( json );
        }
        catch ( Exception e )
        {
            LOG.warn( e.getMessage(), e );
        }

        return new JsonEntryToResponseCursor<Response>();
    }


    /**
     * {@inheritDoc}
     */
    public Cursor<Response> search( DN baseDn, String filter, SearchScope scope, String... attributes )
        throws LdapException
    {
        // generate some random operation number
        SearchRequest searchRequest = new SearchRequestImpl();

        searchRequest.setBase( baseDn );
        searchRequest.setFilter( filter );
        searchRequest.setScope( scope );
        searchRequest.addAttributes( attributes );
        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );

        return search( searchRequest );
    }


    /**
     * {@inheritDoc}
     */
    public Cursor<Response> search( String baseDn, String filter, SearchScope scope, String... attributes )
        throws LdapException
    {
        return search( new DN( baseDn ), filter, scope, attributes );
    }


    /**
     * {@inheritDoc}
     */
    public void unBind() throws Exception
    {
        messageId.set( 0 );
        if ( client != null )
        {
            HttpPost post = new HttpPost( unBindUri );
            post.setHeader( sessionHeader );
            HttpResponse response = client.execute( post );
            LOG.debug( "unbind request succeeded with return code {}", response.getStatusLine().getStatusCode() );
            client.getConnectionManager().shutdown();
            sessionHeader = null;
            client = null;
        }
    }


    /**
     * {@inheritDoc}
     */
    public ExtendedResponse extended( String oid ) throws LdapException
    {
        throw new UnsupportedOperationException(
            "extended operations are not supported on CoreSession based connection" );
    }


    /**
     * {@inheritDoc}
     */
    public ExtendedResponse extended( ExtendedRequest extendedRequest ) throws LdapException
    {
        return extended( ( String ) null );

    }


    /**
     * {@inheritDoc}
     */
    public ExtendedResponse extended( OID oid, byte[] value ) throws LdapException
    {
        return extended( ( String ) null );
    }


    /**
     * {@inheritDoc}
     */
    public ExtendedResponse extended( OID oid ) throws LdapException
    {
        return extended( ( String ) null );
    }


    /**
     * {@inheritDoc}
     */
    public ExtendedResponse extended( String oid, byte[] value ) throws LdapException
    {
        return extended( ( String ) null );
    }


    /**
     * {@inheritDoc}
     */
    public void setTimeOut( long timeOut )
    {
        throw new UnsupportedOperationException( "setting timeout is not supported on CoreSession" );
    }


    /**
     * {@inheritDoc}
     */
    public void abandon( AbandonRequest abandonRequest )
    {
        throw new UnsupportedOperationException( "abandon operation is not supported" );
    }


    /**
     * {@inheritDoc}
     */
    public void abandon( int messageId )
    {
        abandon( null );
    }


    /**
     * {@inheritDoc}
     */
    public BindResponse bind() throws LdapException, IOException
    {
        BindRequest bindReq = new BindRequestImpl();
        bindReq.setName( DN.EMPTY_DN );
        bindReq.setCredentials( ( byte[] ) null );

        return bind( bindReq );
    }


    /**
     * {@inheritDoc}
     */
    public BindResponse bind( BindRequest bindRequest ) throws LdapException, IOException
    {
        int newId = messageId.incrementAndGet();
        bindRequest.setMessageId( newId );

        BindResponse bindResp = null;

        try
        {
            connect();

            HttpPost post = new HttpPost( bindUri );
            post.setEntity( new StringEntity( JsonBuilder.get().toJson( bindRequest ) ) );

            HttpResponse resp = client.execute( post );
            sessionHeader = resp.getHeaders( AppConstants.SESSION_ID )[0];

            System.out.println( "received session header " + sessionHeader );

            HttpEntity entity = resp.getEntity();
            String json = EntityUtils.toString( entity );

            LOG.debug( "received json {}", json );

            bindResp = JsonBuilder.get().fromJson( json, BindResponseImpl.class );
        }
        catch ( IOException e )
        {
            LOG.warn( e.getMessage(), e );
            throw e;
        }

        return bindResp;
    }


    /**
     * {@inheritDoc}
     */
    public BindResponse bind( DN name, String credentials ) throws LdapException, IOException
    {
        byte[] credBytes = ( credentials == null ? StringTools.EMPTY_BYTES : StringTools.getBytesUtf8( credentials ) );

        BindRequest bindReq = new BindRequestImpl();
        bindReq.setName( name );
        bindReq.setCredentials( credBytes );

        return bind( bindReq );
    }


    /**
     * {@inheritDoc}
     */
    public BindResponse bind( String name, String credentials ) throws LdapException, IOException
    {
        return bind( new DN( name ), credentials );
    }


    private void addResponseControls( ResultResponseRequest iReq, Message clientResp )
    {
        Collection<Control> ctrlSet = iReq.getResultResponse().getControls().values();

        for ( Control c : ctrlSet )
        {
            clientResp.addControl( c );
        }
    }

    /**
     * fetches the rootDSE from the server
     * @throws LdapException
     */
    public Entry fetchRootDSE() throws LdapException
    {
        Cursor<Response> cursor = null;
        try
        {
            cursor = search( "", "(objectClass=*)", SearchScope.OBJECT, "*", "+" );
            cursor.next();
            SearchResultEntry searchRes = ( SearchResultEntry ) cursor.get();

            rootDSE = searchRes.getEntry();
        }
        catch ( Exception e )
        {
            String msg = "Failed to fetch the RootDSE";
            LOG.error( msg );
            throw new LdapException( msg, e );
        }
        finally
        {
            if ( cursor != null )
            {
                try
                {
                    cursor.close();
                }
                catch ( Exception e )
                {
                    LOG.error( "Failed to close open cursor", e );
                }
            }
        }
        
        return rootDSE;
    }


    public static void main( String[] args ) throws Exception
    {
        LdapHttpConnection con = new LdapHttpConnection( new URL( "http://localhost:7009/adminapp/ldap" ) );
        BindResponse resp = con.bind( "uid=admin,ou=system", "secret" );
        System.out.println( resp );

        //        con.loadSchema();
        //        JsonBuilder.setSchemaManager( con.getSchemaManager() );

        Entry entry = con.lookup( "uid=admin,ou=system" );
        System.out.println( entry );

        con.unBind();
        con.bind( "uid=admin,ou=system", "secret" );

        ModifyRequest modReq = new ModifyRequestImpl();
        modReq.setName( entry.getDn() );
        modReq.add( SchemaConstants.CN_AT, "http administrator" );

        //        System.out.println( con.modify( modReq ) );

        entry = con.lookup( "uid=admin,ou=system" );
        System.out.println( entry );

        Cursor<Response> cursor = con.search( "cn=apache,ou=schema", "(objectClass=*)", SearchScope.ONELEVEL,
            "objectClass" );
        while ( cursor.next() )
        {
            //            System.out.println( ( ( SearchResultEntry ) cursor.get() ).getEntry() );
        }

        cursor.close();
    }
}
